﻿using System;
using System.Linq;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using EnterpriseLibraryExtensions.Events.Aggregation;

namespace EnterpriseLibraryExtensions.Events.ServiceModel
{
    /// <summary>
    /// The <see cref="EventAggregationScopeBehaviorAttribute"/> class makes it possible to decorate a service to automatically
    /// use event aggregation at either service or operation level. If no exceptions are thrown in the service - the scope completes automatically.
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class EventAggregationScopeBehaviorAttribute : Attribute, IOperationBehavior
    {
        /// <summary>
        /// The NoOptions instance is used whenever a scope is created with the type NoFilteringOptions.
        /// To optimize performance, a single instance is used in all cases.
        /// </summary>
        private static readonly IScopeOptions NoOptions = new NoFilteringOptions();

        /// <summary>
        /// Initializes a new instance of the <see cref="EventAggregationScopeBehaviorAttribute"/> class.
        /// </summary>
        public EventAggregationScopeBehaviorAttribute()
            : this(typeof(NoFilteringOptions))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EventAggregationScopeBehaviorAttribute"/> class.
        /// </summary>
        /// <param name="scopeOptionsType">Type of the scope options. Type must exposte the <see cref="IScopeOptions"/> interface.</param>
        public EventAggregationScopeBehaviorAttribute(Type scopeOptionsType)
        {
            if (scopeOptionsType == null)
            {
                throw new ArgumentNullException("scopeOptionsType");
            }

            ScopeOptionsType = scopeOptionsType;

            if (scopeOptionsType == typeof(NoFilteringOptions))
            {
                ScopeOptions = NoOptions;
            }
            else
            {
                if (!scopeOptionsType.GetInterfaces().Any(i => i == typeof(IScopeOptions)))
                {
                    throw new ArgumentException(@"scopeOptionsType must expose IScopeOptions interface", "scopeOptionsType");
                }

                ScopeOptions = (IScopeOptions)Activator.CreateInstance(scopeOptionsType);
            }
        }

        /// <summary>
        /// Gets the instance that holds the passed-in scope options type.
        /// The instance might point to the <see cref="NoOptions">NoOptions</see> instance.
        /// </summary>
        public IScopeOptions ScopeOptions { get; private set; }

        /// <summary>
        /// Gets the type of the scope options.
        /// </summary>
        /// <value>
        /// The type of the scope options.
        /// </value>
        public Type ScopeOptionsType { get; private set; }

        /// <summary>
        /// Implement to confirm that the operation meets some intended criteria.
        /// </summary>
        /// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param>
        public void Validate(OperationDescription operationDescription)
        {
        }

        /// <summary>
        /// Implements a modification or extension of the service across an operation.
        /// </summary>
        /// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param>
        /// <param name="dispatchOperation">The run-time object that exposes customization properties for the operation described by <paramref name="operationDescription"/>.</param>
        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            if (dispatchOperation == null)
            {
                throw new ArgumentNullException("dispatchOperation");
            }

            var parameterInspectors = dispatchOperation.ParameterInspectors;

            parameterInspectors.Add(new EventAggregationScopeInspector(ScopeOptions));
        }

        /// <summary>
        /// Implements a modification or extension of the client across an operation.
        /// </summary>
        /// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param>
        /// <param name="clientOperation">The run-time object that exposes customization properties for the operation described by <paramref name="operationDescription"/>.</param>
        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
        }

        /// <summary>
        /// Implement to pass data at runtime to bindings to support custom behavior.
        /// </summary>
        /// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param>
        /// <param name="bindingParameters">The collection of objects that binding elements require to support the behavior.</param>
        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
        }
    }
}
